package com.msh.frame.web.handler;

import com.msh.frame.client.common.ErrorCode;
import com.msh.frame.client.common.Result;
import com.msh.frame.client.exception.BizException;
import com.msh.frame.client.exception.ServerException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.ArrayList;
import java.util.List;


/**
 * 使用result来处理异常请求
 */
@Slf4j
public class ResultHandlerExceptionResolver implements HandlerExceptionResolver {

  @Override
  public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception ex) {
    try {
      if (ex instanceof BizException) {
        return errorResultModelAndView(methodBizException((BizException) ex));
      } else if (ex instanceof ServerException) {
        return errorResultModelAndView(methodServerException((ServerException) ex));
      } else if (ex instanceof MethodArgumentNotValidException) {
        return errorResultModelAndView(methodArgumentNotValidException((MethodArgumentNotValidException) ex));
      } else if (ex instanceof BindException) {
        return errorResultModelAndView(bindException((BindException) ex));
      } else if (ex instanceof HttpMessageNotReadableException) {
        //前端传递数据错误, HttpMessageNotReadableException 是 NestedRuntimeException 的子类
        HttpMessageNotReadableException e = (HttpMessageNotReadableException) ex;
        log.warn("拦截到异常{}", "HttpMessageNotReadableException", e);
        return errorResultModelAndView(ErrorCode.PARAM_ERROR.appendMes(e.getLocalizedMessage()));
      } else if (ex instanceof NestedRuntimeException) {
        log.warn("拦截到异常{}", "NestedRuntimeException", ex);
        return errorResultModelAndView(methodNestedRuntimeException(ex));
      } else if (ex instanceof MissingServletRequestParameterException) {
        return errorResultModelAndView(missingServletRequestParameterException(ex));
      } else if (ex instanceof UndeclaredThrowableException) {
        return errorResultModelAndView(undeclaredThrowableException(ex));
      } else if (ex instanceof HttpRequestMethodNotSupportedException) {
        return handleHttpRequestMethodNotSupported(
            (HttpRequestMethodNotSupportedException) ex);
      }
    } catch (Exception handlerEx) {
      if (log.isWarnEnabled()) {
        log.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]",
            handlerEx);
      }
    }

    log.warn("拦截到异常, 方法:{}", "resolveException", ex);
    return errorResultModelAndView(ErrorCode.SYSTEM_ERROR);

  }



  private ModelAndView handleHttpRequestMethodNotSupported(
      HttpRequestMethodNotSupportedException ex) {
    log.warn("拦截到异常, 方法:{}", "handleHttpRequestMethodNotSupported", ex);
    return errorResultModelAndView(
        ErrorCode.REQUEST_METHOD_ERROR.appendMes(ex.getLocalizedMessage()));
  }

  private ModelAndView errorResultModelAndView(ErrorCode errorCode) {
    return errorResultModelAndView(errorCode.getCode(), errorCode.getMsg());
  }

  private ModelAndView errorResultModelAndView(Result<Void> result) {
    return errorResultModelAndView(result.getCode(), result.getMsg());
  }

  private ModelAndView errorResultModelAndView(Integer code, String msg) {
    ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
    mav.addObject("code", code);
    mav.addObject("msg", msg);
    mav.addObject("success", false);
    return mav;
  }

  public Result<Void> methodBizException(BizException e) {
    return Result.errorReturn(ErrorCode.PARAM_ERROR.getCode(), e.getMessage());
  }

  public Result<Void> methodServerException(ServerException e) {
    String msg = e.getMessage();
    Throwable cause = e.getCause();
    log.error(msg, cause);
    return Result.errorReturn(ErrorCode.SYSTEM_ERROR.getCode(), msg);
  }

  public Result<Void> methodNestedRuntimeException(Exception e) {
    NestedRuntimeException ex = (NestedRuntimeException) e;
    Throwable throwable = ex.getCause();
    if (null != throwable) {
      if(throwable instanceof SQLIntegrityConstraintViolationException){
        return Result.errorReturn(ErrorCode.PARAM_ERROR.getCode(), "数据重复");
      }
      throwable = throwable.getCause();
      if (throwable instanceof ServerException) {
        return methodServerException((ServerException) throwable);
      }
    }
    return Result.errorReturn(ErrorCode.SYSTEM_ERROR);
  }

  public Result<Void> methodArgumentNotValidException(MethodArgumentNotValidException e) {
    log.warn("拦截到异常, 方法:{}", "MethodArgumentNotValidException", e);
    List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
    StringBuilder sb = new StringBuilder();
    fieldErrors.forEach(f -> {
      sb.append(f.getField())
          .append(f.getDefaultMessage())
          .append(" ");
    });
    return Result.errorReturn(ErrorCode.PARAM_ERROR.getCode(), sb.toString());
  }

  private Result<Void> exception(Exception e) {
    log.warn("拦截到异常, 方法:{}", "exception", e);
    return Result.errorReturn(ErrorCode.SYSTEM_ERROR);
  }

  private static Result<Void> undeclaredThrowableException(Exception e) {
    UndeclaredThrowableException ex = (UndeclaredThrowableException) e;
    log.warn("拦截到异常, 方法:{}", "undeclaredThrowableException", ex);
    return Result.errorReturn(ErrorCode.UNKNOWN_ERROR);
  }

  private Result<Void> bindException(BindException ex) {
    List<ObjectError> objectErrorList = ex.getAllErrors();
    List<String> list = new ArrayList<>(objectErrorList.size());
    if (objectErrorList.size() > 0) {
      for (ObjectError objectError : objectErrorList) {
        list.add(objectError.getDefaultMessage());
      }
    }
    log.warn("拦截到异常, 方法:{}", "bindException", ex);
    return Result.errorReturn(ErrorCode.PARAM_ERROR.getCode(), String.join(",", list));
  }

  private Result<Void> missingServletRequestParameterException(Exception e) {
    MissingServletRequestParameterException ex = (MissingServletRequestParameterException) e;
    log.warn("拦截到异常, 方法:{}", "missingServletRequestParameterException", ex);
    return Result.errorReturn(ErrorCode.MISS_PARAM.appendMes(ex.getParameterName()));
  }
}
